package github.sf.fw.tools.db.connector;

import com.alibaba.druid.pool.DruidDataSource;
import github.sf.fw.tools.ExtClassLoader;
import github.sf.fw.tools.db.DSConnector;
import github.sf.fw.tools.db.DSProperty;
import github.sf.fw.tools.db.JDBCDataSource;
import github.sf.fw.tools.db.ResultSetWrapper;
import github.sf.fw.tools.db.model.ResultData;
import github.sf.fw.tools.db.model.ResultRow;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class JdbcConnector implements DSConnector {
    private final DataSource dataSource;
    protected DSProperty dsProperty;

    public JdbcConnector(DSProperty properties) {
        try {
            this.dsProperty = properties;
            //    int maxWaitMill = 1000;
            String ds = "ds";
            if ("ds".equals(dsProperty.getMeta().get(ds))) {
                dataSource = initDruidDataSource(properties);
            } else {
                dataSource = initJdbcDataSource();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * <pre>
     *     druidDataSource.setUrl("jdbc:mysql://localhost:3306/tally?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&serverTimezone=UTC");
     * </pre>
     *
     * @param properties 连接属性
     * @return 数据源
     * @throws SQLException sql异常
     */
    private DataSource initDruidDataSource(DSProperty properties) throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(properties.getUrl());
        druidDataSource.setUsername(properties.getUsername());
        druidDataSource.setPassword(properties.getPassword());
        druidDataSource.setDbType(properties.getDbType());
        druidDataSource.setInitialSize(1);
        druidDataSource.setMinIdle(1);
        druidDataSource.setConnectionErrorRetryAttempts(0);
        druidDataSource.setRemoveAbandoned(true);
        druidDataSource.setMaxWait(100L);
        druidDataSource.setKeepAlive(false);
        druidDataSource.setTestOnBorrow(true);
        druidDataSource.setBreakAfterAcquireFailure(true);
        try {
            Class<?> driverClass = initDriver(properties);
            Driver driver = (Driver) driverClass.newInstance();
            druidDataSource.setDriver(driver);
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return druidDataSource;
    }

    private DataSource initJdbcDataSource() {
        JDBCDataSource jdbcDataSource = new JDBCDataSource(dsProperty);
        try {
            Class<?> aClass = initDriver(dsProperty);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return jdbcDataSource;
    }

    private Class<?> initDriver(DSProperty properties) throws ClassNotFoundException {
        Class<?> driverClass;
        if (properties.getDriverPath() != null && !properties.getDriverPath().isEmpty()) {
            ExtClassLoader spiClassLoader = new ExtClassLoader(properties.getDriverPath());
            driverClass = Class.forName(properties.getDriver(), false, spiClassLoader);
        } else {
            driverClass = Class.forName(properties.getDriver());
        }
        return driverClass;
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    public static JdbcConnector getInst(DSProperty properties) {
        return new JdbcConnector(properties);
    }

    public Connection getConn() throws SQLException {
        DataSource dataSource = getDataSource();
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            close();
            throw e;
        }
    }

    /**
     * 更新
     *
     * @param sql  sql语句
     * @param args 参数
     * @return 更新条数
     * @throws SQLException 异常
     */
    public int doUpdate(String sql, Object[] args) throws SQLException {
        Connection conn = getConn();
        int i = doUpdate(sql, args, conn);
        closeResource(conn);
        return i;
    }

    public int doUpdate(String sql, Object[] args, Connection conn) throws SQLException {
        PreparedStatement stmt = null;
        int ret = 0;
        try {
            stmt = conn.prepareStatement(sql);
            if (args != null) {
                for (int i = 0; i < args.length; i++) {
                    stmt.setObject(i + 1, args[i]);
                }
            }
            ret = stmt.executeUpdate();
        } catch (Exception e) {
            closeConnAndDataSource(conn);
            handleError(e);
        } finally {
            closeResource(stmt);
        }
        return ret;
    }

    private void closeConnAndDataSource(Connection connection) {
        closeResource(connection);
        close();
    }

    /**
     * 查询，这个是自动提交的
     */
    public List<ResultRow> doQuery(String sql, Object[] args) throws SQLException {
        Connection conn = getConn();
        List<ResultRow> resultRows = doQuery(sql, args, conn);
        closeResource(conn);
        return resultRows;
    }

    public List<ResultRow> doQuery(String sql, Object[] args, Connection conn) throws SQLException {
        PreparedStatement stmt = null;
        List<ResultRow> resultRows = null;
        try {
            stmt = conn.prepareStatement(sql);
            if (args != null) {
                for (int i = 0; i < args.length; i++) {
                    stmt.setObject(i + 1, args[i]);
                }
            }
            boolean execute = stmt.execute();
            resultRows = new ArrayList<>();
            if (execute) {
                ResultSetMetaData metaData = stmt.getMetaData();
                ResultSet resultSet = stmt.getResultSet();
                TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
                while (resultSet.next()) {
                    ResultRow resultRow = new ResultRow();
                    for (int i = 0; i < metaData.getColumnCount(); i++) {
                        ResultData resultData = new ResultData();
                        String columnName = metaData.getColumnName(i + 1);
                        int columnType = metaData.getColumnType(i + 1);
                        //                    Object obj = resultSet.getObject(i + 1);
                        resultData.setColumnIndex(i + 1);
                        resultData.setColumnName(columnName);
                        resultData.setColumnType(columnType);
                        TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(JdbcType.forCode(columnType));
                        Object value = typeHandler.getResult(resultSet, i + 1);
                        if (value != null) {
                            resultData.setJavaType(value.getClass());
                        }
                        resultData.setColumnValue(value);
                        //                    resultData.setColumnValue(obj);
                        resultRow.add(resultData);
                    }
                    resultRows.add(resultRow);
                }
            }
        } catch (Exception e) {
            closeConnAndDataSource(conn);
            handleError(e);
        }
        //关闭stmt
        closeResource(stmt);
        return resultRows;
    }


    public ResultSetWrapper doQueryWith(String sql, Object[] args, Connection conn) throws SQLException {
        PreparedStatement stmt = null;
        try {
            stmt = conn.prepareStatement(sql);
            if (args != null) {
                for (int i = 0; i < args.length; i++) {
                    stmt.setObject(i + 1, args[i]);
                }
            }
            boolean execute = stmt.execute();
            if (execute) {
                ResultSetMetaData metaData = stmt.getMetaData();
                int columnCount = metaData.getColumnCount();
                ResultSetWrapper resultSetWrapper = new ResultSetWrapper(stmt.getResultSet(), columnCount);
                resultSetWrapper.setStatement(stmt);
                for (int i = 0; i < columnCount; i++) {
                    ResultData resultData = new ResultData();
                    resultData.setColumnIndex(i + 1);
                    resultData.setColumnName(metaData.getColumnName(i + 1));
                    resultData.setColumnType(metaData.getColumnType(i + 1));
                    resultSetWrapper.add(resultData);
                }
                return resultSetWrapper;
            }
        } catch (Exception e) {
            closeConnAndDataSource(conn);
            handleError(e);
        } finally {
            //关闭stmt
            closeResource(stmt);
        }
        return null;
    }

    @Override
    public void close() {
        if (dataSource != null && dataSource instanceof DruidDataSource) {
            DruidDataSource druidDataSource = (DruidDataSource) dataSource;
            druidDataSource.close();
        }
    }

    public void closeResource(AutoCloseable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void handleError(Throwable e) throws SQLException {
        if (e instanceof SQLException) {
            throw (SQLException) e;
        } else {
            throw new RuntimeException(e.getCause());
        }
    }
}
